﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using NUnit.Framework;
using NUnit.Framework.SyntaxHelpers;
using Rhino.Mocks;
using SimpleServiceBus.Bus;
using SimpleServiceBus.Bus.Subscriptions;
using SimpleServiceBus.Endpoint;
using SimpleServiceBus.Messages.ControlMessages;
using SimpleServiceBus.Messages.Subscriptions;
using SimpleServiceBus.Tests.Stubs;

namespace SimpleServiceBus.Tests.Bus.Subscriptions
{
    [TestFixture]
    public class DefaultSubscriptionManagerTestFixture
    {
        private static DefaultSubscriptionManager CreateManager()
        {
            var bus = MockRepository.GenerateStub<IMessageBus>();
            var repository = MockRepository.GenerateStub<ISubscriptionRepository>();

            var mgr = new DefaultSubscriptionManager(bus, repository);

            return mgr;
        }

        private static IList<StoredSubscription> GetSampleSubscriptions()
        {
            var subs = new List<StoredSubscription>
                           {
                               new StoredSubscription {MessageType = typeof (string), Subscriber = "s1"},
                               new StoredSubscription {MessageType = typeof (string), Subscriber = "s2"},
                               new StoredSubscription {MessageType = typeof (int), Subscriber = "s3"}
                           };
            return subs;
        }

        [Test]
        public void Subscribe_To_All_Subscribes_To_All_Types()
        {
            DefaultSubscriptionManager mgr = CreateManager();
            mgr.Repository.Stub(action => action.GetAllSubscriptions()).Return(new List<StoredSubscription>());

            mgr.Start();

            mgr.SubscribeToAll("helloThere");

            Assert.That(mgr.GetSubscribersForMessage("tada")[0], Is.EqualTo("helloThere"));
            Assert.That(mgr.GetSubscribersForMessage(3)[0], Is.EqualTo("helloThere"));
            Assert.That(mgr.GetSubscribersForMessage(new TestBus())[0], Is.EqualTo("helloThere"));
        }

        [Test]
        public void Subscribe_To_Message_Called_Multiple_Times_Will_Aggregate_Subscribers()
        {
            DefaultSubscriptionManager mgr = CreateManager();
            mgr.Repository.Stub(action => action.GetAllSubscriptions()).Return(new List<StoredSubscription>());

            mgr.Start();

            mgr.SubscribeToMessage(typeof (IEnumerable), "s1");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s1");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s2");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s3");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s4");
            mgr.SubscribeToMessage(typeof (string), "s5");

            IList<string> subs = mgr.GetSubscribersForMessage(new[] {"a"});

            Assert.That(subs.Count, Is.EqualTo(4));
            Assert.That(subs.Contains("s1"));
            Assert.That(subs.Contains("s2"));
            Assert.That(subs.Contains("s3"));
            Assert.That(subs.Contains("s4"));
        }

        [Test]
        public void Subscribe_To_Message_Subscribes_To_Specific_Message()
        {
            DefaultSubscriptionManager mgr = CreateManager();
            mgr.Repository.Stub(action => action.GetAllSubscriptions()).Return(new List<StoredSubscription>());

            mgr.Start();

            mgr.SubscribeToMessage(typeof (IEnumerable), "s1");

            Assert.That(mgr.GetSubscribersForMessage(new object[0])[0], Is.EqualTo("s1"));
            Assert.That(mgr.GetSubscribersForMessage(3).Count, Is.EqualTo(0));
        }

        [Test]
        public void Unsubscribe_From_All_Removes_Subs_For_Subscriber_But_Not_Other_Subscribers()
        {
            DefaultSubscriptionManager mgr = CreateManager();
            mgr.Repository.Stub(action => action.GetAllSubscriptions()).Return(new List<StoredSubscription>());

            mgr.Start();

            mgr.SubscribeToMessage(typeof (IEnumerable), "s1");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s1");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s3");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s4");
            mgr.SubscribeToMessage(typeof (int), "s1");

            Assert.That(mgr.GetSubscribersForMessage(new string[0]).Count, Is.EqualTo(3));
            Assert.That(mgr.GetSubscribersForMessage(5).Count, Is.EqualTo(1));

            mgr.UnsubscribeFromAll("s1");

            IList<string> subs = mgr.GetSubscribersForMessage(new string[0]);

            Assert.That(subs.Count, Is.EqualTo(2));
            Assert.That(subs.Contains("s3"));
            Assert.That(subs.Contains("s4"));

            Assert.That(mgr.GetSubscribersForMessage(5).Count, Is.EqualTo(0));
        }

        [Test]
        public void Unsubscribe_From_Message_Removes_Specific_Sub_And_None_Others()
        {
            DefaultSubscriptionManager mgr = CreateManager();
            mgr.Repository.Stub(action => action.GetAllSubscriptions()).Return(new List<StoredSubscription>());

            mgr.Start();

            mgr.SubscribeToMessage(typeof (IEnumerable), "s1");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s1");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s3");
            mgr.SubscribeToMessage(typeof (IEnumerable), "s4");
            mgr.SubscribeToMessage(typeof (int), "s1");


            Assert.That(mgr.GetSubscribersForMessage(new string[0]).Count, Is.EqualTo(3));
            Assert.That(mgr.GetSubscribersForMessage(5).Count, Is.EqualTo(1));

            mgr.UnsubscribeFromMessage(typeof (IEnumerable), "s1");

            IList<string> subs = mgr.GetSubscribersForMessage(new string[0]);

            Assert.That(subs.Count, Is.EqualTo(2));
            Assert.That(subs.Contains("s3"));
            Assert.That(subs.Contains("s4"));

            Assert.That(mgr.GetSubscribersForMessage(5).Count, Is.EqualTo(1));
            Assert.That(mgr.GetSubscribersForMessage(5).Contains("s1"));
        }

        [Test]
        public void Will_Load_Subscriptions_From_Repository_When_Started()
        {
            DefaultSubscriptionManager mgr = CreateManager();

            mgr.Repository.Stub(action => action.GetAllSubscriptions()).Return(GetSampleSubscriptions());

            mgr.Start();

            IList<string> subs = mgr.GetSubscribersForMessage("hello");
            Assert.That(subs.Count, Is.EqualTo(2));
            Assert.That(subs[0], Is.EqualTo("s1"));
            Assert.That(subs[1], Is.EqualTo("s2"));

            subs = mgr.GetSubscribersForMessage(1);
            Assert.That(subs.Count, Is.EqualTo(1));
            Assert.That(subs[0], Is.EqualTo("s3"));
        }

        [Test]
        public void Will_Start_Without_Error_With_Missing_Repository()
        {
            var mgr = new DefaultSubscriptionManager
                          {
                              Endpoint = MockRepository.GenerateStub<IMessagingEndpoint>()
                          };
            mgr.Endpoint.Stub(action => action.LocalServices).Return(new EndpointServiceManager(mgr.Endpoint));

            mgr.Start();

            //No exception means the test passed
        }

        [Test]
        public void ShouldSuccessfullyRegisterMultipleSimultaneousSubscriptionRequests()
        {
            //Given
            DefaultSubscriptionManager mgr = new DefaultSubscriptionManager();

            //When
            for (int i = 0; i < 1000;i++)
            {
                Type messageType = (i%2 == 0) ? typeof (TestBusMessage) : typeof (ReadyMessage);
                Action a = () => mgr.SubscribeToMessage(messageType, Guid.NewGuid().ToString());
                a.BeginInvoke(null, null);
            }
            Thread.Sleep(500);
            //Then
            Assert.That(mgr.GetSubscribersForMessage(new TestBusMessage()).Count, Is.EqualTo(500));
        }
    }
}